package edu.jit.nsi.iot_ms.serviceimpl.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import edu.jit.nsi.iot_ms.domain.*;
import edu.jit.nsi.iot_ms.mapper.AutoActionDAO;
import edu.jit.nsi.iot_ms.mapper.EnvTmCtlDAO;
import edu.jit.nsi.iot_ms.mapper.WarnDAO;
import edu.jit.nsi.iot_ms.mapper.EnvThCtlDAO;
import edu.jit.nsi.iot_ms.mapper.dbDaoMap.EnviHisAggrRsp;
import edu.jit.nsi.iot_ms.serviceimpl.TermRspHandler;
import edu.jit.nsi.iot_ms.serviceimpl.custom.EquipServiceImpl;
import edu.jit.nsi.iot_ms.serviceimpl.custom.TerminalServiceImpl;
import edu.jit.nsi.iot_ms.transport.tcp.EquipNewRsp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;

enum ParamLvl {
    // 因为已经定义了带参数的构造器，所以在列出枚举值时必须传入对应的参数
    DWACT(0), DWWARN(1), NORMAL(2), UPWARN(3), UPACT(4);

    // 定义一个 private 修饰的实例变量
    private int level;

    // 定义一个带参数的构造器，枚举类的构造器只能使用 private 修饰
    private ParamLvl(int d) {
        this.level = d;
    }
    // 定义 get set 方法
    public  int getDate() {
        return level;
    }
    public void setDate(int d) {
        this.level = d;
    }
}

@Slf4j
@Service
public class EvnCtlServiceImpl {
    public static final int Scan_Min = 5;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    SimpleDateFormat tm_sdf = new SimpleDateFormat("HH:mm:ss");
    private Map<String, Integer> cltCellMap = new HashMap<>();
    private String tm_last = null;
    private String dt_last = null;

    @Autowired
    AppServiceImpl appService;

    @Autowired
    EnvThCtlDAO envThCtlDAO;

    @Autowired
    EnvTmCtlDAO envTmCtlDAO;

    @Autowired
    EquipServiceImpl equipService;

    @Autowired
    WarnDAO warnDAO;

    @Autowired
    AutoActionDAO atactDAO;

    @Autowired
    JitServiceImpl minaService;

    @Autowired
    TerminalServiceImpl termService;

    @Autowired
    LoraServiceImpl loraService;
    /**
     * 告警日志记录
     * @param cell_id  生产单元集合
     * @param start_time  开始时间
     * @param end_time  结束时间
     */
    public List<WarnDO> warnQuery(int cell_id, String start_time, String end_time){
        return warnDAO.selectList(new EntityWrapper<WarnDO>()
                .eq("cellid", cell_id).between("time", start_time, end_time));
    }


    /**
     * 自动控制日志记录
     * @param cell_id  生产单元集合
     * @param start_time  开始时间
     * @param end_time  结束时间
     */
    public List<AutoActionDO> autoActQuery(int cell_id, String start_time, String end_time){
        return atactDAO.selectList(new EntityWrapper<AutoActionDO>()
                .eq("cellid", cell_id).between("time", start_time, end_time));
    }


    /**
     * 延迟（5秒钟）依次打开或关闭塘口下的所有设备
     * @param type 环境参数
     * @param optfg 操作指令 0:关闭   1:打开
     * @return
     */
    private synchronized boolean delayOptEqp(int cellId, String type, int optfg, float value, Date date){
        //首先判断是否已经操作完毕
        if(cltCellMap.containsKey(cellId+"#"+type)&&cltCellMap.get(cellId+"#"+type)==optfg) {
            if(optfg==1)
                log.info("自动控制[{}]塘口[{}]环境参数已启动.", cellId, type);
            else
                log.info("自动控制[{}]塘口[{}]环境参数已关闭.", cellId, type);
            return true;
        }
        log.info("自动控制[{}]塘口[{}]环境参数[{}]启动...", cellId, type, optfg);
        //打开塘口下所有类型为type的设备

        List<EquipDO> eqplist = equipService.cellParamEqp_list(cellId,type);
        List<EquipNewRsp> equipStaList = appService.onCellEqpQuery(cellId);
        for(EquipDO eq : eqplist){
            for(EquipNewRsp eqsta : equipStaList){
                if(eqsta.getEpid() == eq.getId()&&eqsta.getStatus()!=optfg) {
                    ctrlCellEquip(eq, optfg, value, date);
                    return false;
                }
            }
        }

        //所有设备都控制成功后，记录状态
        log.info("自动控制[{}]塘口[{}]环境参数[{}]操作已完成.", cellId, type, optfg);
        cltCellMap.put(cellId + "#" + type, optfg);
        atactDAO.insert(new AutoActionDO(cellId, type, optfg, value, date));
        return true;
    }



    private void ctrlCellEquip(EquipDO equipData, int optfg, float value, Date date){
        TermDO termDO= termService.getTermById(equipData.getTermid());

        TermRspHandler handler = new TermRspHandler() {
            //控制成功要执行回调函数
            public void execute() {
                log.info("control succeed. call callback");
                delayOptEqp(equipData.getCellid(), equipData.getType(), optfg, value, date);
            }

            //控制超时要执行回调函数
            public void timeout() {
                log.info("control timeover. call callback");
                delayOptEqp(equipData.getCellid(), equipData.getType(), optfg, value, date);
            }
        };
        if(termDO.getType()==1) {
            //通过minaservice发送control请求，并注册回调函数
            minaService.sendCntl(2, equipData.getTermid(),
                    equipData.getAddr(), equipData.getRoad(), optfg, handler);
        }else if(termDO.getType()==5){
            loraService.downLinkCtrl(2, termDO.getDeveui(),
                    equipData.getAddr(), equipData.getRoad(), optfg, handler);
        }
    }


    private ParamLvl paramLevel(float value , EnvThrsdCtlDO envctl){
        if(value < envctl.getActdw())
            return ParamLvl.DWACT;
        else if(value < envctl.getWndw())
            return ParamLvl.DWWARN;
        else if(value < envctl.getWnup())
            return ParamLvl.NORMAL;
        else if(value < envctl.getActup())
            return ParamLvl.UPWARN;
        else
            return ParamLvl.UPACT;
    }

    /**
     * 延迟（5秒钟）依次打开或关闭塘口下的所有设备
     * @param values 塘口ID
     * @param envctl 环境参数
     * @return  DWACT:下限操作值, DWWARN:下限告警值, NORMAL:正常值, UPWARN:上限告警值, UPACT:上限操作值
     */
    private ParamLvl judgePL(List<Float> values , EnvThrsdCtlDO envctl){
        int[] nums = {0,0,0,0,0};
        int maxpos=0;
        for(Float va : values){
            ParamLvl lvl = paramLevel(va , envctl);
            nums[lvl.ordinal()]++;
        }

        for(int i=0; i<5; i++){
            if(nums[i]>nums[maxpos])
                maxpos = i;
        }

        return ParamLvl.values()[maxpos];
    }

    /**
     * 基于阈值的检查操作
     * @param now 当前时间
     * @param start 前一轮(开始)时间
     * @param end  当前(结束)时间
     * @return  null
     */
    private void thresdHandle(Date now, String start, String end){
        List<EnvThrsdCtlDO> autoctl = envThCtlDAO.selectList(new EntityWrapper<EnvThrsdCtlDO>());
        for (EnvThrsdCtlDO autoCell : autoctl) {
            int cellId = autoCell.getCellid();
            boolean isAuto = autoCell.getAutofg()==1? true: false;
            int cid = autoCell.getCellid();
            String pa = autoCell.getParam();
            EnviHisAggrRsp.SNTV sntv = appService.onParamHisQuery(autoCell.getParamid(), start, end);
            List<Float> values = sntv.getValues();
            if(values.size() ==0)
                continue;
            float lstva = values.get(values.size() - 1);
            ParamLvl level = judgePL(values, autoCell);
            switch (level) {
                case DWACT:
                    WarnDO autoact_dw = new WarnDO(cid, pa, true, 1, lstva, now);
                    warnDAO.insert(autoact_dw);
                    if(isAuto){
                        delayOptEqp(cellId, autoCell.getParam(), 1, lstva, now);
                    }
                    break;
                case DWWARN:
                    WarnDO autowarn_dw = new WarnDO(cid, pa, true, 0, lstva, now);
                    warnDAO.insert(autowarn_dw);
                    break;

                case UPACT:
                    WarnDO autoact_up = new WarnDO(cid, pa, false, 1, lstva, now);
                    warnDAO.insert(autoact_up);
                    if(isAuto){
                        delayOptEqp(cellId, autoCell.getParam(), 1, lstva, now);
                    }
                    break;
                case UPWARN:
                    WarnDO autowarn_up = new WarnDO(cid, pa, false, 0, lstva, now);
                    warnDAO.insert(autowarn_up);
                    break;

                case NORMAL:
                    delayOptEqp(cellId, autoCell.getParam(), 0, lstva, now);
                    break;
            }
        }
    }


    /**
     * 基于时间的检查操作
     * @param now 开始时间
     * @param last 前一轮的时间
     * @return  null
     */
    private void timeHandle(String last, String now, Date date_now) {
        List<EnvTimeCtlDO> autoctl = envTmCtlDAO.selectList(new EntityWrapper<EnvTimeCtlDO>().eq("autofg",1));
        for (EnvTimeCtlDO timeCtl : autoctl) {
            int cellId = timeCtl.getCellid();
            String tmpoint = timeCtl.getTime();
            if(timeBetween(tmpoint, last, now)) {
                delayOptEqp(cellId, timeCtl.getParam(), timeCtl.getOpt(), 0, date_now);
            }
        }
    }


    /**
     *
     * @param nowDate   要比较的时间
     * @param startDate   开始时间
     * @param endDate   结束时间
     * @return   true在时间段内，false不在时间段内
     * @throws Exception
     */
    public static boolean timeBetween(String nowDate, String startDate, String endDate){
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        Date now,start,end;
        try{
            now = format.parse(nowDate);
            start = format.parse(startDate);
            end = format.parse(endDate);
        }catch (Exception e){
            log.error("[timeBetween]format time(String) to 24(HH:mm:ss) error!");
            return  false;
        }

        long nowTime = now.getTime();
        long startTime = start.getTime();
        long endTime = end.getTime();

        return nowTime >= startTime && nowTime <= endTime;
    }

    /**
     * 周期扫描Map中的等待响应缓存
     */
    @Scheduled(fixedDelay = Scan_Min*60*1000, initialDelay=30*1000)
    private void periodChkCtl() {
        Date now = new Date();
        if(dt_last==null || tm_last==null){
            Date last = new Date(now.getTime() - Scan_Min * 60 * 1000); //上个扫描周期的时间
            try {
                tm_last = tm_sdf.format(last);
                dt_last = sdf.format(last);
            }catch (Exception e){
                log.error("[periodChkCtl] format date(yyyy) to 24 time(HH:mm:mm) error!");
                return;
            }
        }

        String tm_now=null;
        String dt_now=null;
        try {
            tm_now = tm_sdf.format(now);
            dt_now = sdf.format(now);
        }catch (Exception e){
            log.error("[periodChkCtl] format date(yyyy) to 24 time(HH:mm:mm) error!");
        }

        thresdHandle(now, dt_last, dt_now);
        timeHandle(tm_last, tm_now, now);
        tm_last = tm_now;
        dt_last = dt_now;
    }
}
